package maple.core;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.FileReader;
import java.io.FileWriter;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.logging.Level;
import java.util.TreeMap;
import java.util.Vector;

import jade.lang.acl.ACLMessage;

import maple.lang.Command;
import maple.lang.Language;
import maple.util.Planner;
import maple.util.Profile;
import maple.util.Sensor;
import maple.util.Task;
import maple.util.tasks.GatewayTask;
import maple.util.Timestamp;

public abstract class Gateway extends Producer
{
	public static final String DELIMITER = "\\.";

	protected TreeMap< String, Profile > _sensors;
	protected HashMap< String, GatewayTask > _tasks;
	
	public Gateway( final int number )
	{
		super();
	
		// from MapleAgent
		_min_arguments = number;
		
		// from Producer
		_names.add( Language.Instruction.INSERT.toString() );
		_names.add( Language.Instruction.DELETE.toString() );
		_names.add( Language.Instruction.UPDATE.toString() );
		_types.add( Sensor.Type.GATEWAY.toString() );
		_types.add( Sensor.Type.GATEWAY.toString() );
		_types.add( Sensor.Type.GATEWAY.toString() );

		_sensors = new TreeMap< String, Profile >();
		_tasks = new HashMap< String, GatewayTask >();
	}

	protected void setup()
	{
		super.setup();
	}

	protected void actionRequest( final ACLMessage message )
	{
		//_logger.info( message.getSender().getLocalName() + Language.DELIMITER + message.getContent() );
		System.out.println( Timestamp.Default.format( new Date( System.currentTimeMillis() ) ) + Language.DELIMITER + message.getSender().getLocalName() + Language.DELIMITER + message.getContent() );
		
		String sender = message.getSender().getName();
		String content = message.getContent();
		String receiver = message.getReplyWith();
		if( receiver == null )
		{
			receiver = sender;
		}
		
		Command.Message result = Command.Message.BAD_COMMAND;
		Command command = new Command( content );
		if( command != null && command.isAvailable() )
		{
			GatewayTask task = new GatewayTask( receiver, command, getName() );
			if( task.isAvailable() && _tasks.containsKey( task.toString() ) )
			{
				result = undo( task );
			}
			else
			{
				switch( command.getInstruction() )
				{
					case READ:
						result = read( task );
						break;
					case WRITE:
						result = write( task );
						break;
					case INSERT:
						result = insert( new Profile( command ) );
						break;
					case UPDATE:
						result = update( new Profile( command ) );
						break;
					case DELETE:
						result = delete( new Profile( command ) );
						break;
				}
			}

			sendMessage( ACLMessage.INFORM, sender, result.toString(), getParameters( task ), null );
		}
		else
		{
			HashMap< String, String > parameters = new HashMap< String, String >();
			parameters.put( Task.Property.COMMAND.toString(), content );
			sendMessage( ACLMessage.INFORM, sender, result.toString(), parameters, null );
		}
	}

	protected void check()
	{
		Vector< GatewayTask > removes = new Vector< GatewayTask >();
		for( GatewayTask task : _tasks.values() )
		{
			long current = Calendar.getInstance().getTimeInMillis();
			long diff = current - task.getTriggerTime();
			long interval = Long.parseLong( task.getProperty( GatewayTask.Property.INTERVAL.toString() ) );
			if( diff >= interval )
			{
				task.setTriggerTime( current );

				String port = task.getProperty( GatewayTask.Property.PORT.toString() );
				if( _sensors.containsKey( port ) )
				{
					String result = "";
					switch( task.getInstruction() )
					{
						case READ:
							result = "" + read( port );
							break;
						case WRITE:
							result = write( port ).toString();
							break;
					}

					if( !sendMessage( ACLMessage.INFORM, task.getProperty( GatewayTask.Property.RECEIVER.toString() ), Timestamp.Default.format( new Date( current ) ) + Language.DELIMITER + result, getParameters( task ), null ) )
					{
						removes.add( task );
					}
				}
				else
				{
					// remove the stale tasks
					removes.add( task );
				}
			}
		}

		for( GatewayTask task : removes )
		{
			_tasks.remove( task.toString() );
		}
	}

	/**
	 * return the status of the delete command with the specified profile
	 *
	 * @param	profile	the specified profile
	 * @return	the status of the delete command with the specified profile
	 *
	 * @author	snowfoxy
	 * @since	2011-11-16
	 */
	private Command.Message delete( final Profile profile )
	{
		if( profile.isAvailable() )
		{
			String port = profile.getProperty( Profile.Property.PORT.toString() );
			if( _sensors.containsKey( port ) )
			{
				if( profile.toString().equals( _sensors.get( port ).toString() ) )
				{
					_sensors.remove( port );
					return modify();
				}
			}
		}
		return Command.Message.BAD_COMMAND;
	}

	/**
	 * return the specified parameters for sending the message or null if the task is not available
	 *
	 * @param	task	the specified task
	 * @return	the specified parameters for sending the message
	 *
	 * @author	snowfoxy
	 * @since	2011-11-16
	 */
	protected HashMap< String, String > getParameters( final GatewayTask task )
	{
		if( task.isAvailable() )
		{
			String port = task.getProperty( GatewayTask.Property.PORT.toString() );
			Profile profile = _sensors.get( port );
			if( profile.isAvailable() )
			{
				HashMap< String, String > parameters = new HashMap< String, String >();
				parameters.put( Task.Property.COMMAND.toString(), task.getCommand() );
				parameters.put( Planner.Property.POSTCONDITION.toString(), profile.toPostcondition() );
				parameters.put( Profile.Property.PORT.toString(), port );
				parameters.put( Profile.Property.TYPE.toString(), profile.getProperty( Profile.Property.TYPE.toString() ) );
				parameters.put( Profile.Property.LOCATION.toString(), profile.getProperty( Profile.Property.LOCATION.toString() ) );
				return parameters;
			}
		}
		return null;
	}

	/**
	 * return the status of the insert command with the specified profile
	 *
	 * @param	profile	the specified profile
	 * @return	the status of the insert command with the specified profile
	 *
	 * @author	snowfoxy
	 * @since	2011-11-16
	 */
	private Command.Message insert( final Profile profile )
	{
		try
		{
			if( profile.isAvailable() )
			{
				String port = profile.getProperty( Profile.Property.PORT.toString() );
				if( !_sensors.containsKey( port ) )
				{
					if( insertPort( port, profile.getProperty( Profile.Property.SENSITIVITY.toString() ) ) )
					{
						_sensors.put( port, profile );
						return modify();
					}
				}
			}
		}
		catch( IllegalArgumentException e )
		{
			_logger.log( Level.WARNING, "", e );
		}
		return Command.Message.BAD_COMMAND;
	}

	/**
	 * return the status of loading the configuration file
	 *
	 * @return	the status of loading the configuration file
	 *
	 * @author	snowfoxy
	 * @since	2011-11-16
	 */
	protected Command.Message load()
	{
		try
		{
			FileReader file = new FileReader( _arguments[ 0 ] );
			BufferedReader reader = new BufferedReader( file );
			
			String content = "";
			while( ( content = reader.readLine() ) != null )
			{
				Profile profile = new Profile( content );
				if( profile.isAvailable() )
				{
					String port = profile.getProperty( Profile.Property.PORT.toString() );
					if( insertPort( port, profile.getProperty( Profile.Property.SENSITIVITY.toString() ) ) )
					{
						_sensors.put( port, profile );
					}
				}
			}

			reader.close();
			file.close();
		
			return Command.Message.OK;
		}
		catch( IOException e )
		{
			_logger.log( Level.WARNING, "", e );
			return Command.Message.EXCEPTION_IO;
		}
	}

	/**
	 * return the status of the read command with the specified task
	 *
	 * @param	task	the specified task
	 * @return	the status of the read command with the specified task
	 *
	 * @author	snowfoxy
	 * @since	2011-11-16
	 */
	private Command.Message read( final GatewayTask task )
	{
		if( task.isAvailable() )
		{
			String port = task.getProperty( GatewayTask.Property.PORT.toString() );
			if( _sensors.containsKey( port ) )
			{
				long interval = Long.parseLong( task.getProperty( GatewayTask.Property.INTERVAL.toString() ) );
				if( interval <= 0 )
				{
					sendMessage( ACLMessage.INFORM, task.getProperty( GatewayTask.Property.RECEIVER.toString() ), Timestamp.Default.format( new Date( Calendar.getInstance().getTimeInMillis() ) ) + Language.DELIMITER + read( port ), getParameters( task ), null );
				}
				else
				{
					_tasks.put( task.toString(), task );
				}
				return Command.Message.OK;
			}
		}
		return Command.Message.BAD_COMMAND;
	}

	/**
	 * return the status of saving the configuration file
	 *
	 * @return	the status of saving the configuration file
	 *
	 * @author	snowfoxy
	 * @since	2011-11-16
	 */
	protected Command.Message save()
	{
		try
		{
			FileWriter writer = new FileWriter( _arguments[ 0 ], false );
			for( String port : _sensors.keySet() )
			{
				Profile profile = _sensors.get( port );
				writer.write( profile.toString() + "\n" );
			}
			writer.close();

			return Command.Message.OK;
		}
		catch( IOException e )
		{
			_logger.log( Level.WARNING, "", e );
			return Command.Message.EXCEPTION_IO;
		}
	}

	/**
	 * return the status of removing the specified task
	 *
	 * @param	task	the specified task
	 * @return	the status of removing the specified task
	 *
	 * @author	snowfoxy
	 * @since	2011-11-16
	 */
	private Command.Message undo( final GatewayTask task )
	{
		_tasks.remove( task.toString() );
		return Command.Message.OK;
	}

	/**
	 * return the status of the update command with the specified profile
	 *
	 * @param	profile	the specified profile
	 * @return	the status of the write command with the specified profile
	 *
	 * @author	snowfoxy
	 * @since	2011-11-16
	 */
	private Command.Message update( final Profile profile )
	{
		if( profile.isAvailable() )
		{
			String port = profile.getProperty( Profile.Property.PORT.toString() );
			if( _sensors.containsKey( port ) )
			{
				if( updatePort( port, profile.getProperty( Profile.Property.SENSITIVITY.toString() ) ) );
				{
					_sensors.put( port, profile );
					return modify();
				}
			}
		}
		return Command.Message.BAD_COMMAND;
	}

	/**
	 * return the status of the write command with the specified task
	 *
	 * @param	task	the specified task
	 * @return	the status of the write command with the specified task
	 *
	 * @author	snowfoxy
	 * @since	2011-11-16
	 */
	private Command.Message write( final GatewayTask task )
	{
		if( task.isAvailable() )
		{
			String port = task.getProperty( GatewayTask.Property.PORT.toString() );
			if( _sensors.containsKey( port ) )
			{
				long interval = Long.parseLong( task.getProperty( GatewayTask.Property.INTERVAL.toString() ) );
				if( interval <= 0 )
				{
					sendMessage( ACLMessage.INFORM, task.getProperty( GatewayTask.Property.RECEIVER.toString() ), Timestamp.Default.format( new Date( Calendar.getInstance().getTimeInMillis() ) ) + Language.DELIMITER + write( port ).toString(), getParameters( task ), null );
				}
				else
				{
					_tasks.put( task.toString(), task );
				}
				return Command.Message.OK;
			}
		}
		return Command.Message.BAD_COMMAND;
	}

	protected abstract boolean insertPort( final String port, final String sensitivity );
	protected abstract boolean updatePort( final String port, final String sensitivity );
	
	protected abstract Command.Message modify();

	protected abstract double read( final String port );
	protected abstract Command.Message write( final String port );
}
